#include "Library_t.h"
#include <iomanip>

using namespace std;

bool Library_t::isUniqueIsbn(const isbn_t & isbn) const {
	return (books_.find(isbn)==books_.end());
}

bool Library_t::isUniqueId(const id_t & id) const {
	return (borrowers_.find(id)==borrowers_.end());
}

Library_t::~Library_t () {
	 map<const id_t, Book_t *>::const_iterator it_books = books_.begin();
	 while (it_books != books_.end()) {
		 delete (*it_books).second;
		 it_books++;
	 }

	 map<const id_t, Borrower_t *>::const_iterator it_borrowers = borrowers_.begin();
	 while (it_borrowers != borrowers_.end()) {
		 delete (*it_borrowers).second;
		 it_borrowers++;
	 }
}

void Library_t::addBook(const isbn_t & isbn, const string & name, const string & author, const int & num_of_copies) {
	
	if (!isUniqueIsbn(isbn))
		throw MultipleIsbnException();

	if (num_of_copies < 1)
		throw InvalidNumOfCopiesException();

	if (isbn < 0)
		throw InvalidISBNException();

	Book_t * pBook = new Book_t(name,author,isbn,num_of_copies);
	books_[isbn] = pBook;
}

void Library_t::removeBook(const isbn_t & isbn)  {
	const Book_t * pBook = searchBook(isbn);
	
	if (pBook == NULL)
		throw BookNotExistsException();

	if (pBook->getNumOfBorrowedCopies() > 0)
		throw CantRemoveBookException();
	
	delete pBook;
	books_.erase(isbn);
}

const Book_t * Library_t::searchBook(const isbn_t & isbn) const {
	map<const isbn_t, Book_t *>::const_iterator it = books_.find(isbn);
	if (it == books_.end())
		return NULL;
	return it->second;

}

void Library_t::addBorrower(const id_t & id,const string & name) {
		
	if (!isUniqueId(id))
		throw MultipleIdException();

	if (id < 0)
		throw InvalidIDException();

	Borrower_t * pBorrower = new Borrower_t(name, id);
	borrowers_[id] = pBorrower;
}


void Library_t::removeBorrower(const id_t & id)  {

	const Borrower_t * pBorrower = searchBorrower(id);
	
	if (pBorrower == NULL)
		throw BorrowerNotExistsException();

	if (pBorrower->getNumOfBorrowedBooks() > 0)
		throw CantRemoveBorrowerException();

	delete pBorrower;
	borrowers_.erase(id);
}

const Borrower_t * Library_t::searchBorrower(const id_t & id) const {
	map<const id_t, Borrower_t *>::const_iterator it = borrowers_.find(id);
	if (it == borrowers_.end())
		return NULL;
	return it->second;

}

bool Library_t::lendBook(const isbn_t & isbn, const id_t & id) {

	Book_t * pBook = const_cast<Book_t*>(searchBook(isbn));
	if (pBook == NULL)
		throw BookNotExistsException();

	Borrower_t * pBorrower = const_cast<Borrower_t*>(searchBorrower(id));
	if (pBorrower == NULL) 
		throw BorrowerNotExistsException();

	bool addedToWaitingList = !(pBook->borrow(pBorrower));
	if (addedToWaitingList) {
		return false;
	}
	pBorrower->borrowBook(pBook);
	return true;

}

void Library_t::returnBook(const isbn_t & isbn, const id_t & id) {
	
	Book_t * pBook = const_cast<Book_t*>(searchBook(isbn));
	if (pBook == NULL)
		throw BookNotExistsException();

	Borrower_t * pBorrower = const_cast<Borrower_t*>(searchBorrower(id));
	if (pBorrower == NULL) 
		throw BorrowerNotExistsException();

	try {
		pBorrower->returnBook(pBook);
		const Borrower_t * pNextBorrower = pBook->returnCopy(pBorrower); //get pointer to waiting borrower
		if (pNextBorrower == NULL) 
			return;
		lendBook(isbn, pNextBorrower->getId());
	} catch (...) {
		throw;
	}

}

 ostream &operator<< (ostream& os, const Library_t & library) {

	 os << "Library printout" << endl << endl;

	 os << "Books:" << endl;
	 os << setfill('=') << setw(50) << "=" << endl;
	 map<const isbn_t, Book_t *>::const_iterator it_books = library.books_.begin();
	 while (it_books != library.books_.end()) {
		 os << *(*it_books).second << endl;
		 it_books++;
	 }

	 os << "Borrowers:" << endl;
	 os << setfill('=') << setw(50) << "=" << endl;
	 map<const id_t, Borrower_t *>::const_iterator it_borrowers = library.borrowers_.begin();
	 while (it_borrowers != library.borrowers_.end()) {
		 os << *(*it_borrowers).second << endl;
		 it_borrowers++;
	 }

	 return os;
 }